package com.smartStatus.auth.controller;

import cn.hutool.core.codec.Base64;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.smartStatus.api.entity.SysMenu;
import com.smartStatus.api.feign.RemoteUserService;
import com.smartStatus.core.api.RedisKey;
import com.smartStatus.core.api.VerficationCode;
import com.smartStatus.core.constant.CacheConstants;
import com.smartStatus.core.constant.SecurityConstant;
import com.smartStatus.core.constant.StatusUser;
import com.smartStatus.core.util.CodeUtils;
import com.smartStatus.core.util.R;
import com.smartStatus.security.annoation.Ignore;
import com.smartStatus.security.utils.SecurityUtils;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cache.CacheManager;
import org.springframework.data.redis.core.ConvertingCursor;
import org.springframework.data.redis.core.Cursor;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ScanOptions;
import org.springframework.data.redis.serializer.JdkSerializationRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.http.HttpHeaders;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.common.OAuth2RefreshToken;
import org.springframework.security.oauth2.provider.AuthorizationRequest;
import org.springframework.security.oauth2.provider.ClientDetails;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.security.oauth2.provider.ClientDetailsService;

import javax.imageio.ImageIO;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.*;
import java.util.concurrent.TimeUnit;

/**
 * @ClassName : AuthController
 * @Author : lby
 * @Date: 2021/5/6 22:18
 * @Description :
 */
@Slf4j
@RestController
@AllArgsConstructor
@RequestMapping("/token")
public class AuthController {
    private static final String smartStatus_OAUTH_ACCESS = SecurityConstant.smartStatus_PREFIX + SecurityConstant.OAUTH_PREFIX + "auth_to_access:";
    private final ClientDetailsService clientDetailsService;
    private final RedisTemplate redisTemplate;
    private final TokenStore tokenStore;
    private final CacheManager cacheManager;
    private final RemoteUserService remoteUserService;


    /**
     * 认证页面
     *
     * @param modelAndView
     * @param error        表单登录失败处理回调的错误信息
     * @return ModelAndView
     */
    @GetMapping("/login")
    public ModelAndView require(ModelAndView modelAndView, @RequestParam(required = false) String error) {
        modelAndView.setViewName("ftl/login");
        modelAndView.addObject("error", error);
        return modelAndView;
    }

    /**
     * 确认授权页面
     *
     * @param request
     * @param session
     * @param modelAndView
     * @return
     */
    @GetMapping("/confirm_access")
    public ModelAndView confirm(HttpServletRequest request, HttpSession session, ModelAndView modelAndView) {
        Map<String, Object> scopeList = (Map<String, Object>) request.getAttribute("scopes");
        modelAndView.addObject("scopeList", scopeList.keySet());

        Object auth = session.getAttribute("authorizationRequest");
        if (auth != null) {
            AuthorizationRequest authorizationRequest = (AuthorizationRequest) auth;
            ClientDetails clientDetails = clientDetailsService.loadClientByClientId(authorizationRequest.getClientId());
            modelAndView.addObject("app", clientDetails.getAdditionalInformation());
            modelAndView.addObject("user", SecurityUtils.getUser());
        }

        modelAndView.setViewName("ftl/confirm");
        return modelAndView;
    }

    /**
     * 退出token
     *
     * @param authHeader Authorization
     */
    @DeleteMapping("/logout")
    public R logout(@RequestHeader(value = HttpHeaders.AUTHORIZATION, required = false) String authHeader) {
        if (StrUtil.isBlank(authHeader) || "undefined".equals(authHeader)) {
            return R.failed("退出失败，token 为空");
        }
        // 退出时清除数据
        if (SecurityUtils.getUser() != null) {
            R tree = remoteUserService.deleteMenuTree(SecurityUtils.getUser().getId(),SecurityConstant.FROM_IN);
        }
        String tokenValue = authHeader.replace(OAuth2AccessToken.BEARER_TYPE, StrUtil.EMPTY).trim();
        return delToken(tokenValue);
    }

    /**
     * 令牌管理调用
     *
     * @param token token
     * @return
     */
    @Ignore
    @DeleteMapping("/{token}")
    public R<Boolean> delToken(@PathVariable("token") String token) {
        OAuth2AccessToken accessToken = tokenStore.readAccessToken(token);
        if (accessToken == null || StrUtil.isBlank(accessToken.getValue())) {
            return R.ok(Boolean.TRUE, "退出失败，token 无效");
        }

        OAuth2Authentication auth2Authentication = tokenStore.readAuthentication(accessToken);
        // 清空用户信息
        cacheManager.getCache(CacheConstants.GLOBALLY + CacheConstants.USER_DETAILS)
                .evict(auth2Authentication.getName());

        // 清空access token
        tokenStore.removeAccessToken(accessToken);

        // 清空 refresh token
        OAuth2RefreshToken refreshToken = accessToken.getRefreshToken();
        tokenStore.removeRefreshToken(refreshToken);
        return R.ok();
    }


    private List<String> findKeysForPage(String patternKey, int pageNum, int pageSize) {
        ScanOptions options = ScanOptions.scanOptions().count(1000L)
                .match(patternKey).build();
        RedisSerializer<String> redisSerializer = (RedisSerializer<String>) redisTemplate.getKeySerializer();
        Cursor cursor = (Cursor) redisTemplate.executeWithStickyConnection(redisConnection -> new ConvertingCursor<>(redisConnection.scan(options), redisSerializer::deserialize));
        List<String> result = new ArrayList<>();
        int tmpIndex = 0;
        int startIndex = (pageNum - 1) * pageSize;
        int end = pageNum * pageSize;

        assert cursor != null;
        while (cursor.hasNext()) {
            if (tmpIndex >= startIndex && tmpIndex < end) {
                result.add(cursor.next().toString());
                tmpIndex++;
                continue;
            }
            if (tmpIndex >= end) {
                break;
            }
            tmpIndex++;
            cursor.next();
        }

        try {
            cursor.close();
        } catch (IOException e) {
            log.error("关闭cursor 失败");
        }
        return result;
    }


    /**
     * 登录获取验证码
     *
     * @return
     * @throws IOException
     */
    @Ignore(value = false)
    @GetMapping("/code")
    public R getCode() throws IOException {
        BufferedImage image = CodeUtils.codeGeneration();
        Map<String, Object> map = new HashMap<>();
//        随机生成uuid
        map.put("uuid", IdUtil.simpleUUID());
        log.info("验证码算式，验证码算式结果【randomString:{},result:{}】", VerficationCode.RANDOM, VerficationCode.CODE_RESULT);
        ByteArrayOutputStream stream = new ByteArrayOutputStream();
        ImageIO.write(image, "png", stream);
        String encode = Base64.encode(stream.toByteArray());
        map.put("img", encode);
//        将验证码信息保存在redis中
        redisTemplate.opsForValue().set(VerficationCode.CODE_PREFIX, VerficationCode.CODE_RESULT, 3, TimeUnit.MINUTES);
        Integer codeResult = (Integer) redisTemplate.opsForValue().get(VerficationCode.CODE_PREFIX);
        return R.ok(map, "登陆验证码生成成功");
    }


    /**
     * 授权测试
     */
    @PreAuthorize("@pms.hasPermission('sys-admin-odidodidodo')")
    @GetMapping("/test/permission")
    public R checkPermission() {
        return R.ok();
    }


}
